# A plugin for adding keywords-handling tags
#
# Release 0.12 (Mar 23, 2005)
#
# This software is provided as-is. You may use it for commercial or 
# personal use. If you distribute it, please keep this notice intact.
#
# Copyright (c) 2005 Hirotaka Ogawa

package MT::Plugin::AllKeywords;
use strict;
use MT::Template::Context;

eval("use Storable;");
if (!$@ && MT->can('add_plugin')) {
    require MT::Plugin;
    my $plugin = new MT::Plugin();
    $plugin->name("AllKeywords Plugin 0.12");
    $plugin->description("Add MTAllKeywords tags for listing blog-wide keywords and entry keywords");
    $plugin->doc_link("http://as-is.net/hacks/2005/03/allkeywords_plugin.html");
    MT->add_plugin($plugin);
}

MT::Template::Context->add_container_tag('AllKeywords' => \&all_keywords);
MT::Template::Context->add_container_tag('EntryAllKeywords' => \&entry_all_keywords);
MT::Template::Context->add_tag('AllKeyword' => \&all_keyword);
MT::Template::Context->add_tag('AllKeywordCount' => \&all_keyword_count);
MT::Template::Context->add_tag('AllKeywordsTotal' => \&all_keywords_total);
MT::Template::Context->add_tag('AllKeywordsTotalSum' => \&all_keywords_total_sum);
MT::Template::Context->add_container_tag('EntriesWithKeywords' => \&entries_with_keywords);
MT::Template::Context->add_container_tag('MostRelatedEntries' => \&most_related_entries);

sub split_keywords {
    my ($string, $delimiter, $case_sensitive) = @_;
    return unless $string;
    my @keywords;
    $string =~ s/^\s+//;
    $string =~ s/\s+$//;
    $string = lc $string unless $case_sensitive;
    return $delimiter ?
	map { s/^\s+//; s/\s+$//; $_ } split($delimiter, $string) :
	split(/\s+/, $string);
}

sub all_keywords {
    my ($ctx, $args, $cond) = @_;

    # sort_by option (keyword/count, default = keyword)
    my $sort_by = $args->{sort_by} || 'keyword';
    # sort_order option (ascend/descend, default = ascend)
    my $sort_order = $args->{sort_order} || 'ascend';
    # lastn option (default = 0, no cutoff)
    my $lastn = $args->{lastn} || 0;
    # delimiter option (default = space characters)
    my $delimiter = $args->{delimiter} || '';
    # case_sensitive option (0/1, default = 1)
    my $case_sensitive = $args->{case_sensitive} || 1;

    my $blog_id = $ctx->stash('blog_id');
    require MT::Entry;
    my $iter = MT::Entry->load_iter({ blog_id => $blog_id,
				      status => MT::Entry::RELEASE() });
    my %all_keywords = ();
    while (my $e = $iter->()) {
	next unless $e->keywords;
	my @keywords = split_keywords($e->keywords, $delimiter, $case_sensitive);
	foreach my $keyword (@keywords) {
	    if (exists($all_keywords{$keyword})) {
		$all_keywords{$keyword}++;
	    } else {
		$all_keywords{$keyword} = 1;
	    }
	}
    }
    my $res = '';
    my $builder = $ctx->stash('builder');
    my $tokens = $ctx->stash('tokens');

    my @list;
    if ($sort_by eq 'keyword') {
	@list = $sort_order eq 'ascend' ?
	    sort { $a cmp $b } keys %all_keywords :
	    sort { $b cmp $a } keys %all_keywords;
    } else {
	@list = $sort_order eq 'ascend' ?
	    sort { $all_keywords{$a} <=> $all_keywords{$b} } keys %all_keywords :
	    sort { $all_keywords{$b} <=> $all_keywords{$a} } keys %all_keywords;
    }

    $ctx->stash('all_keywords_total', scalar(@list));

    my $total_sum = 0;
    foreach (@list) {
	$total_sum += $all_keywords{$_};
    }
    $ctx->stash('all_keywords_total_sum', $total_sum);

    my $i = 0;
    foreach (@list) {
	last if $lastn && $i >= $lastn;
	$ctx->stash('all_keyword', $case_sensitive ? $_ : ucfirst $_);
	$ctx->stash('all_keyword_count', $all_keywords{$_});
	defined(my $out = $builder->build($ctx, $tokens))
	    or return $ctx->error($ctx->errstr);
	$res .= $out;
	$i++;
    }
    $res;
}

sub entry_all_keywords {
    my ($ctx, $args, $cond) = @_;
    my $e = $ctx->stash('entry')
	or return $ctx->_no_entry_error('MTEntryAllKeywords');
    return '' unless $e->keywords;

    # delimiter option (default = space characters)
    my $delimiter = $args->{delimiter} || '';
    # case_sensitive option (0/1, default = 1)
    my $case_sensitive = $args->{case_sensitive} || 1;

    my @keywords = split_keywords($e->keywords, $delimiter, $case_sensitive);
    my $res = '';
    my $builder = $ctx->stash('builder');
    my $tokens = $ctx->stash('tokens');
    my $total = scalar(@keywords);
    $ctx->stash('all_keywords_total', $total);
    $ctx->stash('all_keywords_total_sum', $total);
    foreach (@keywords) {
	$ctx->stash('all_keyword', $case_sensitive ? $_ : ucfirst $_);
	$ctx->stash('all_keyword_count', 1);
	defined(my $out = $builder->build($ctx, $tokens))
	    or return $ctx->error($ctx->errstr);
	$res .= $out;
    }
    $res;
}

sub all_keyword {
    $_[0]->stash('all_keyword');
}

sub all_keyword_count {
    $_[0]->stash('all_keyword_count');
}

sub all_keywords_total {
    $_[0]->stash('all_keywords_total');
}

sub all_keywords_total_sum {
    $_[0]->stash('all_keywords_total_sum');
}

sub entries_with_keywords {
    my ($ctx, $args, $cond) = @_;

    # keywords option (must be specified)
    my $keywords = $args->{keywords} or return '';
    # delimiter option (default = space characters)
    my $delimiter = $args->{delimiter} || '';
    # case_sensitive option (0/1, default = 0)
    my $case_sensitive = $args->{case_sensitive} || 0;
    # sort_by option (title/status/created_on/modified_on/author_id/excerpt, default = created_on)
    my $sort_by = $args->{sort_by} || 'created_on';
    # sort_order option (ascend/descend, default = ascend)
    my $sort_order = $args->{sort_order} || 'descend';
    # lastn option (default = 0, no cutoff)
    my $lastn = $args->{lastn} || 0;

    my $blog_id = $ctx->stash('blog_id');
    require MT::Entry;
    my $iter = MT::Entry->load_iter({ blog_id => $blog_id,
				      status => MT::Entry::RELEASE() },
				    { sort => $sort_by,
				      direction => $sort_order });
    my @entries;
    my @patterns = split_keywords($keywords, $delimiter, $case_sensitive);
    my $i = 0;
    while (my $e = $iter->()) {
	last if $lastn && $i >= $lastn;
	next unless $e->keywords;
	my @keywords = split_keywords($e->keywords, $delimiter, $case_sensitive);
	my $check = 1;
	foreach my $pattern (@patterns) {
	    unless (scalar grep { $_ eq $pattern } @keywords) {
		$check = 0;
		last;
	    }
	}
	if ($check) {
	    push @entries, $e;
	    $i++;
	}
    }
    return '' unless @entries;

    my $res = '';
    my $tokens = $ctx->stash('tokens');
    my $builder = $ctx->stash('builder');
    $i = 0;
    for my $e (@entries) {
	local $ctx->{__stash}{entry} = $e;
	local $ctx->{current_timestamp} = $e->created_on;
	local $ctx->{modification_timestamp} = $e->modified_on;
	my $out = $builder->build($ctx, $tokens, {
	    %$cond,
	    EntryIfExtended => $e->text_more ? 1 : 0,
	    EntryIfAllowComments => $e->allow_comments,
	    EntryIfCommentsOpen => $e->allow_comments && $e->allow_comments eq '1',
	    EntryIfAllowPings => $e->allow_pings,
	    EntriesHeader => !$i,
	    EntriesFooter => !defined $entries[$i+1]
	    });
	return $ctx->error($ctx->errstr) unless defined $out;
	$res .= $out;
	$i++;
    }
    $res;
}

sub most_related_entries {
    my ($ctx, $args, $cond) = @_;
    my $entry = $ctx->stash('entry')
	or return $ctx->_no_entry_error('MTMostRelatedEntries');
    return '' unless $entry->keywords;

    # delimiter option (default = space characters)
    my $delimiter = $args->{delimiter} || '';
    # case_sensitive option (0/1, default = 0)
    my $case_sensitive = $args->{case_sensitive} || 0;
    # lastn option (default = 0, no cutoff)
    my $lastn = $args->{lastn} || 0;

    my @patterns = split_keywords($entry->keywords, $delimiter, $case_sensitive);

    my $blog_id = $ctx->stash('blog_id');
    require MT::Entry;
    my $iter = MT::Entry->load_iter({ blog_id => $blog_id,
				      status => MT::Entry::RELEASE() },
				    { sort => 'created_on',
				      direction => 'descend' });
    my @entries;
    my %matches = ();
    while (my $e = $iter->()) {
	next unless $e->keywords;
	next if $e->id == $entry->id;
	my @keywords = split_keywords($e->keywords, $delimiter, $case_sensitive);
	my $count = 0;
	foreach my $pattern (@patterns) {
	    $count++ if (scalar grep { $_ eq $pattern } @keywords);
	}
	if ($count) {
	    push @entries, $e;
	    $matches{ $e->id } = $count;
	}
    }
    return '' unless @entries;

    @entries = sort { $matches{$b->id} <=> $matches{$a->id} } @entries;
    splice(@entries, $lastn) if $lastn && (scalar @entries > $lastn);

    my $res = '';
    my $tokens = $ctx->stash('tokens');
    my $builder = $ctx->stash('builder');
    my $i = 0;
    for my $e (@entries) {
	local $ctx->{__stash}{entry} = $e;
	local $ctx->{current_timestamp} = $e->created_on;
	local $ctx->{modification_timestamp} = $e->modified_on;
	my $out = $builder->build($ctx, $tokens, {
	    %$cond,
	    EntryIfExtended => $e->text_more ? 1 : 0,
	    EntryIfAllowComments => $e->allow_comments,
	    EntryIfCommentsOpen => $e->allow_comments && $e->allow_comments eq '1',
	    EntryIfAllowPings => $e->allow_pings,
	    EntriesHeader => !$i,
	    EntriesFooter => !defined $entries[$i+1]
	    });
	return $ctx->error($ctx->errstr) unless defined $out;
	$res .= $out;
	$i++;
    }
    $res;
}

eval("use MT::XSearch;");
if (!$@) {
    MT::XSearch->add_search_plugin('AllKeywords', {
	label => 'Keyword Search',
	description => 'Keyword Search plugin for MT-XSearch',
	on_execute => \&xsearch_on_execute,
	on_stash => \&xsearch_on_stash });
}

sub xsearch_on_stash {
    $_[0]->stash('entry', $_[1]);
    $_[0]->{current_timestamp} = $_[1]->created_on;
    $_[0]->{modification_timestamp} = $_[1]->modified_on;
}

sub xsearch_on_execute {
    my $args = shift;
    require MT::Entry;
    my $blog_id = $args->{blog_id} or
	MT->error('A Blog ID is required.');
    my $sort_by = $args->{sort_by} || 'created_on';
    my $sort_order = $args->{sort_order} || 'descend';
    my $delimiter = $args->{delimiter} || '';
    my $case_sensitive = $args->{case_sensitive} || 0;

    my @patterns = split_keywords($args->{search}, $delimiter, $case_sensitive);
    my $iter = MT::Entry->load_iter({ blog_id => $blog_id,
				      status => MT::Entry::RELEASE() },
				    { sort => $sort_by, 
				      direction => $sort_order });
    my @results;
    while (my $e = $iter->()) {
	next unless $e->keywords;
	my @keywords = split_keywords($e->keywords, $delimiter, $case_sensitive);
	my $check = 1;
	foreach my $pattern (@patterns) {
	    unless (scalar grep { $_ eq $pattern } @keywords) {
		$check = 0;
		last;
	    }
	}
	push @results, $e if $check;
    }
    \@results;
}

1;
